//*******************************************************************
// Demo OpenCL application to compute a simple vector addition
// computation between 2 arrays on the GPU
// ******************************************************************
#include <stdio.h>
#include <stdlib.h>
#include <CL/cl.h>
#include <iostream>
// OpenCL source code

const char* OpenCLSource[] = {
"__kernel void VectorCompute(__global float* output_velX, __global float* output_velY, __global float* output_velZ,__global float* output_posX, __global float* output_posY, __global float* output_posZ,__global float* input_velX, __global float* input_velY, __global float* input_velZ, __global float* input_posX, __global float* input_posY, __global float* input_posZ, __global float* input_forceX, __global float* input_forceY, __global float* input_forceZ,__global float* d)",
"{",
" // Index of the elements to add \n",
" unsigned int n = get_global_id(0);",
" // newVelocity = velocity + force * dt; \n",
"output_velX[n] = input_velX[n] + input_forceX[n] * (*d); output_velY[n] = input_velY[n] + input_forceY[n] * (*d); output_velZ[n] = input_velZ[n] + input_forceZ[n] * (*d); output_posX[n] = input_posX[n] + output_velX[n] * (*d); output_posY[n] = input_posY[n] + output_velY[n] * (*d); output_posZ[n] = input_posZ[n] + output_velZ[n] * (*d);",
"}"
};
/*
const char* OpenCLSource[] = {
  "__kernel void VectorCompute( __global float* output_velX, __global float* output_velY, __global float* output_velZ, __global float* output_posX, __global float* output_posY, __global float* output_posZ, __global float* input_velX, __global float* input_velY, __global float* input_velZ,__global float* input_posX, __global float* input_posY, __global float* input_posZ,__global float* input_forceX, __global float* input_forceY, __global float* input_forceZ,__global float *dt)",
"{",
" // Index of the elements to add \n",
" unsigned int n = get_global_id(0);",
" // newVelocity = velocity + force * dt; \n",
" output_velX[n] = input_velX[n] + input_forceX[n] * (*dt);",
" output_velY[n] = input_velY[n] + input_forceY[n] * (*dt);",
" output_velZ[n] = input_velZ[n] + input_forceZ[n] * (*dt);",
" // newPosition = position + newVelocity * (*dt); \n",
" output_posX[n] = input_posX[n] + output_velX[n] * (*dt);",
" output_posY[n] = input_posY[n] + output_velY[n] * (*dt);",
" output_posZ[n] = input_posZ[n] + output_velZ[n] * (*dt);",
"}"
};
*/
using namespace std;
/*
void testVectorCompute(int index, float* output, float* input, float* df)
{
    output[index] = input[index] * (*df);
    printf("%f %f %f %f %f %f %f %f %f \n",output[index][0],output[index][1],output[index][2],
               output[index][3],output[index][4],output[index][5],
               output[index][6],output[index][7],output[index][8]);
}
 */

// Some interesting data for the vectors
int InitialData1[20] = {37,50,54,50,56,0,43,43,74,71,32,36,16,43,56,100,50,25,15,17};
int InitialData2[20] = {35,51,54,58,55,32,36,69,27,39,35,40,16,44,55,14,58,75,18,15};
// Number of elements in the vectors to be added
#define SIZE 48
// Main function
// *********************************************************************
int main(int argc, char **argv)
{
    
      
      float dt = 1.55;
      
      float *tfx_VelocityX = new float[SIZE];
      float *tfx_VelocityY = new float[SIZE];
      float *tfx_VelocityZ = new float[SIZE];
      
      float *tfx_PositionX = new float[SIZE];
      float *tfx_PositionY = new float[SIZE];
      float *tfx_PositionZ = new float[SIZE];
      
      float *tfx_ForceX = new float[SIZE];
      float *tfx_ForceY = new float[SIZE];
      float *tfx_ForceZ = new float[SIZE];
      
      float *tfx_outVelocityX = new float[SIZE];
      float *tfx_outVelocityY = new float[SIZE];
      float *tfx_outVelocityZ = new float[SIZE];
      
      float *tfx_outPositionX = new float[SIZE];
      float *tfx_outPositionY = new float[SIZE];
      float *tfx_outPositionZ = new float[SIZE];
      
      
      
      for (int i = 0 ; i < SIZE; i++)
      {
	tfx_VelocityX[i] = (float)i;
	tfx_VelocityY[i] = (float)i;
	tfx_VelocityZ[i] = (float)i;
	tfx_PositionX[i] = (float)i;
	tfx_PositionY[i] = (float)i;
	tfx_PositionZ[i] = (float)i;
	tfx_ForceX[i] = (float)i;
	tfx_ForceY[i] = (float)i;
	tfx_ForceZ[i] = (float)i;
	
      }
    
    cout << *OpenCLSource << endl;
    
    
    cl_int ciErrNum ;
    //Get an OpenCL platform
    cl_platform_id cpPlatform;
    clGetPlatformIDs(1, &cpPlatform, NULL);
    
    // Get a GPU device
    cl_device_id cdDevice;
    clGetDeviceIDs(cpPlatform, CL_DEVICE_TYPE_GPU, 1, &cdDevice, NULL);
    
    // Create a context to run OpenCL on our CUDA-enabled NVIDIA GPU
    cl_context GPUContext = clCreateContext(0, 1, &cdDevice, NULL, NULL, &ciErrNum);
    //cl_context GPUContext = clCreateContextFromType(0, CL_DEVICE_TYPE_GPU, NULL, NULL, &ciErrNum);
    if (GPUContext == NULL)
    {
    //GPUContext = clCreateContextFromType(cl_properties, CL_DEVICE_TYPE_CPU, NULL, NULL, &ciErrNum);
        switch(ciErrNum)
        {
            case CL_INVALID_PLATFORM://
                printf("if properties is NULL and no platform could be selected or if platform value specified in properties is not a valid platform.\n");
            break;
            case CL_INVALID_VALUE://  
                printf("if context property name in properties is not a supported property name, or if pfn_notify is NULL but user_data is not NULL.\n");
                printf("if pfn_notify is NULL but user_data is not NULL.\n");
             break;
            case CL_DEVICE_NOT_AVAILABLE://  
                printf("if no devices that match device_type are currently available.\n");
             break;
            case CL_DEVICE_NOT_FOUND://  
                printf("if no devices that match device_type were found.");
             break;
            case CL_OUT_OF_HOST_MEMORY :// 
                printf("if there is a failure to allocate resources required by the OpenCL implementation on the host.\n");
                 break;
            case CL_INVALID_DEVICE_TYPE://  
                printf("if device_type is not a valid value.\n");
                 break;
        }
        return 0;
    }
    
    // Create a command-queue on the GPU device
    cl_command_queue cqCommandQueue = clCreateCommandQueue(GPUContext, cdDevice, 0, NULL);
    
    cl_mem GPUVector1_tfx_VelocityX = clCreateBuffer(GPUContext, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, sizeof(float) * SIZE, tfx_VelocityX, NULL);
    cl_mem GPUVector1_tfx_VelocityY = clCreateBuffer(GPUContext, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, sizeof(float) * SIZE, tfx_VelocityY, NULL);
    cl_mem GPUVector1_tfx_VelocityZ = clCreateBuffer(GPUContext, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, sizeof(float) * SIZE, tfx_VelocityZ, NULL);
    
    cl_mem GPUVector1_tfx_PositionX = clCreateBuffer(GPUContext, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, sizeof(float) * SIZE, tfx_PositionX, NULL);
    cl_mem GPUVector1_tfx_PositionY = clCreateBuffer(GPUContext, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, sizeof(float) * SIZE, tfx_PositionY, NULL);
    cl_mem GPUVector1_tfx_PositionZ = clCreateBuffer(GPUContext, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, sizeof(float) * SIZE, tfx_PositionZ, NULL);
    
    cl_mem GPUVector1_tfx_ForceX = clCreateBuffer(GPUContext, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, sizeof(float) * SIZE, tfx_ForceX, NULL);
    cl_mem GPUVector1_tfx_ForceY = clCreateBuffer(GPUContext, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, sizeof(float) * SIZE, tfx_ForceY, NULL);
    cl_mem GPUVector1_tfx_ForceZ = clCreateBuffer(GPUContext, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, sizeof(float) * SIZE, tfx_ForceZ, NULL);
    
    cl_mem GPU_df = clCreateBuffer(GPUContext, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, sizeof(float), &dt, NULL);
    
    cl_mem GPUOutputVectorVelocityX = clCreateBuffer(GPUContext, CL_MEM_WRITE_ONLY,sizeof(float) * SIZE , NULL, NULL);
    cl_mem GPUOutputVectorVelocityY = clCreateBuffer(GPUContext, CL_MEM_WRITE_ONLY,sizeof(float) * SIZE , NULL, NULL);
    cl_mem GPUOutputVectorVelocityZ = clCreateBuffer(GPUContext, CL_MEM_WRITE_ONLY,sizeof(float) * SIZE , NULL, NULL);
    
    cl_mem GPUOutputVectorPositionX = clCreateBuffer(GPUContext, CL_MEM_WRITE_ONLY,sizeof(float) * SIZE , NULL, NULL);
    cl_mem GPUOutputVectorPositionY = clCreateBuffer(GPUContext, CL_MEM_WRITE_ONLY,sizeof(float) * SIZE , NULL, NULL);
    cl_mem GPUOutputVectorPositionZ = clCreateBuffer(GPUContext, CL_MEM_WRITE_ONLY,sizeof(float) * SIZE , NULL, NULL);
        
    // Create OpenCL program with source code
    cl_program OpenCLProgram = clCreateProgramWithSource(GPUContext, 7,  OpenCLSource, NULL, &ciErrNum);
    if (OpenCLProgram == NULL)
    {
        if (CL_INVALID_CONTEXT == ciErrNum)
        {
            printf("Error clCreateProgramWithSource : CL_INVALID_CONTEXT\n\n");
        }
        else if (CL_INVALID_VALUE == ciErrNum)
        {
            printf("Error clCreateProgramWithSource : CL_INVALID_VALUE\n\n");
        }
        else if (CL_OUT_OF_HOST_MEMORY == ciErrNum)
        {
            printf("Error clCreateProgramWithSource : CL_OUT_OF_HOST_MEMORY\n\n");
        }
        return 0;
    }
    
    // Build the program (OpenCL JIT compilation)
    clBuildProgram(OpenCLProgram, 0, NULL, NULL, NULL, NULL);
    
    // Create a handle to the compiled OpenCL function (Kernel)
    cl_kernel OpenCLVectorAdd = clCreateKernel(OpenCLProgram, "VectorCompute", &ciErrNum);
    if (ciErrNum != CL_SUCCESS)
    {
        switch(ciErrNum)
        {
            case CL_INVALID_PROGRAM:
                printf("Error clCreateKernel : CL_INVALID_PROGRAM\n\n");
                break;
            case  CL_INVALID_PROGRAM_EXECUTABLE:
                printf("Error clCreateKernel : CL_INVALID_PROGRAM_EXECUTABLE\n\n");
                break;
            case CL_INVALID_KERNEL_NAME:
                printf("Error clCreateKernel : CL_INVALID_KERNEL_NAME\n\n");
                break;
            case CL_INVALID_VALUE:
                printf("Error clCreateKernel : CL_INVALID_VALUE\n\n");
                break;
        }
    }
    
    // In the next step we associate the GPU memory with the Kernel arguments
    clSetKernelArg(OpenCLVectorAdd, 0, sizeof(cl_mem),(void*)&GPUOutputVectorVelocityX);
    clSetKernelArg(OpenCLVectorAdd, 1, sizeof(cl_mem),(void*)&GPUOutputVectorVelocityY);
    clSetKernelArg(OpenCLVectorAdd, 2, sizeof(cl_mem),(void*)&GPUOutputVectorVelocityZ);
    
    clSetKernelArg(OpenCLVectorAdd, 3, sizeof(cl_mem),(void*)&GPUOutputVectorPositionX);
    clSetKernelArg(OpenCLVectorAdd, 4, sizeof(cl_mem),(void*)&GPUOutputVectorPositionY);
    clSetKernelArg(OpenCLVectorAdd, 5, sizeof(cl_mem),(void*)&GPUOutputVectorPositionZ);
    
    clSetKernelArg(OpenCLVectorAdd, 6, sizeof(cl_mem),(void*)&GPUVector1_tfx_VelocityX);
    clSetKernelArg(OpenCLVectorAdd, 7, sizeof(cl_mem),(void*)&GPUVector1_tfx_VelocityY);
    clSetKernelArg(OpenCLVectorAdd, 8, sizeof(cl_mem),(void*)&GPUVector1_tfx_VelocityZ);
    
    clSetKernelArg(OpenCLVectorAdd, 9, sizeof(cl_mem),(void*)&GPUVector1_tfx_PositionX);
    clSetKernelArg(OpenCLVectorAdd, 10, sizeof(cl_mem),(void*)&GPUVector1_tfx_PositionY);
    clSetKernelArg(OpenCLVectorAdd, 11, sizeof(cl_mem),(void*)&GPUVector1_tfx_PositionZ);
    
    clSetKernelArg(OpenCLVectorAdd, 12, sizeof(cl_mem),(void*)&GPUVector1_tfx_ForceX);
    clSetKernelArg(OpenCLVectorAdd, 13, sizeof(cl_mem),(void*)&GPUVector1_tfx_ForceY);
    clSetKernelArg(OpenCLVectorAdd, 14, sizeof(cl_mem),(void*)&GPUVector1_tfx_ForceZ);
    
    clSetKernelArg(OpenCLVectorAdd, 15, sizeof(cl_mem), (void*)&GPU_df);
    
    // Launch the Kernel on the GPU
    size_t WorkSize[1] = {SIZE }; // one dimensional Range
    ciErrNum = clEnqueueNDRangeKernel(cqCommandQueue, OpenCLVectorAdd, 1, NULL, WorkSize, NULL, 0, NULL, NULL);
    if (ciErrNum != CL_SUCCESS)
    {
        switch(ciErrNum)
        {
            case CL_INVALID_PROGRAM_EXECUTABLE:
                printf("Error clEnqueueNDRangeKernel : CL_INVALID_PROGRAM_EXECUTABLE\n\n");
                break;
            case CL_INVALID_COMMAND_QUEUE:
                printf("Error clEnqueueNDRangeKernel : CL_INVALID_COMMAND_QUEUE\n\n");
                break;
            case CL_INVALID_KERNEL:
                printf("Error clEnqueueNDRangeKernel : CL_INVALID_KERNEL\n\n");
                break;
             case   CL_INVALID_CONTEXT:
                printf("Error clEnqueueNDRangeKernel : CL_INVALID_CONTEXT\n\n");
                break;
             case   CL_INVALID_KERNEL_ARGS:
                printf("Error clEnqueueNDRangeKernel : CL_INVALID_KERNEL_ARGS\n\n");
                break;
             case   CL_INVALID_WORK_DIMENSION:
                printf("Error clEnqueueNDRangeKernel : CL_INVALID_WORK_DIMENSION\n\n");
                break;
             case   CL_INVALID_WORK_GROUP_SIZE:
                printf("Error clEnqueueNDRangeKernel : CL_INVALID_WORK_GROUP_SIZE\n\n");
                break;
             case   CL_INVALID_WORK_ITEM_SIZE:
                printf("Error clEnqueueNDRangeKernel : CL_INVALID_WORK_ITEM_SIZE\n\n");
                break;
             case   CL_INVALID_GLOBAL_OFFSET:
                printf("Error clEnqueueNDRangeKernel : CL_INVALID_GLOBAL_OFFSET\n\n");
                break;
             case   CL_OUT_OF_RESOURCES:
                printf("Error clEnqueueNDRangeKernel : CL_OUT_OF_RESOURCES\n\n");
                break;
             case   CL_MEM_OBJECT_ALLOCATION_FAILURE:
                printf("Error clEnqueueNDRangeKernel : CL_MEM_OBJECT_ALLOCATION_FAILURE\n\n");
                break;
             case   CL_INVALID_EVENT_WAIT_LIST:
                printf("Error clEnqueueNDRangeKernel : CL_INVALID_EVENT_WAIT_LIST\n\n");
                break;
             case   CL_OUT_OF_HOST_MEMORY:
                printf("Error clEnqueueNDRangeKernel : CL_OUT_OF_HOST_MEMORY\n\n");
                break;
                
                
        }
        return 0;
    }
    
    // Copy the output in GPU memory back to CPU memory
    
    clEnqueueReadBuffer(cqCommandQueue, GPUOutputVectorVelocityX, CL_TRUE, 0, SIZE * sizeof(float), tfx_outVelocityX, 0, NULL, NULL);
    clEnqueueReadBuffer(cqCommandQueue, GPUOutputVectorVelocityY, CL_TRUE, 0, SIZE * sizeof(float), tfx_outVelocityY, 0, NULL, NULL);
    clEnqueueReadBuffer(cqCommandQueue, GPUOutputVectorVelocityZ, CL_TRUE, 0, SIZE * sizeof(float), tfx_outVelocityZ, 0, NULL, NULL);
    
    clEnqueueReadBuffer(cqCommandQueue, GPUOutputVectorPositionX, CL_TRUE, 0, SIZE * sizeof(float), tfx_outPositionX, 0, NULL, NULL);
    clEnqueueReadBuffer(cqCommandQueue, GPUOutputVectorPositionY, CL_TRUE, 0, SIZE * sizeof(float), tfx_outPositionY, 0, NULL, NULL);
    clEnqueueReadBuffer(cqCommandQueue, GPUOutputVectorPositionZ, CL_TRUE, 0, SIZE * sizeof(float), tfx_outPositionZ, 0, NULL, NULL);
    
    // Cleanup
    clReleaseKernel(OpenCLVectorAdd);
    clReleaseProgram(OpenCLProgram);
    clReleaseCommandQueue(cqCommandQueue);
    clReleaseContext(GPUContext);
    clReleaseMemObject(GPUVector1_tfx_VelocityX);
    clReleaseMemObject(GPUVector1_tfx_VelocityY);
    clReleaseMemObject(GPUVector1_tfx_VelocityZ);
    
    clReleaseMemObject(GPUVector1_tfx_PositionX);
    clReleaseMemObject(GPUVector1_tfx_PositionY);
    clReleaseMemObject(GPUVector1_tfx_PositionZ);
    
    clReleaseMemObject(GPUVector1_tfx_ForceX);
    clReleaseMemObject(GPUVector1_tfx_ForceY);
    clReleaseMemObject(GPUVector1_tfx_ForceZ);
    
    
    clReleaseMemObject(GPU_df);
    
    clReleaseMemObject(GPUOutputVectorVelocityX);
    clReleaseMemObject(GPUOutputVectorVelocityY);
    clReleaseMemObject(GPUOutputVectorVelocityZ);
    clReleaseMemObject(GPUOutputVectorPositionX);
    clReleaseMemObject(GPUOutputVectorPositionY);
    clReleaseMemObject(GPUOutputVectorPositionZ);
    
    // Print out the results
    for (int i = 0 ; i < SIZE; i++)
      {
        
        
	cout <<  tfx_outVelocityX[i] << " " <<  tfx_outVelocityY[i] << " " << tfx_outVelocityZ[i] << endl;
	cout <<  tfx_outPositionX[i] << " " << tfx_outPositionY[i] << " " << tfx_outPositionZ[i] << endl;
	cout << endl;
      }
      
    
      delete []  tfx_VelocityX;
      delete []  tfx_VelocityY;
      delete []  tfx_VelocityZ;
      
      delete []  tfx_PositionX;
      delete []  tfx_PositionY;
      delete []  tfx_PositionZ;
      
      delete []  tfx_ForceX;
      delete []  tfx_ForceY;
      delete []  tfx_ForceZ;
      
      delete []  tfx_outVelocityX;
      delete []  tfx_outVelocityY;
      delete []  tfx_outVelocityZ;
      
      delete []  tfx_outPositionX;
      delete []  tfx_outPositionY;
      delete []  tfx_outPositionZ;
    
    
    printf("\n\nThe End\n\n");
    
    
    
    char cBuffer[1024];
    ciErrNum = clGetPlatformInfo (cpPlatform, CL_PLATFORM_NAME, sizeof(cBuffer), cBuffer, NULL);
    printf("PLATFORM NAME: %s\n",cBuffer);
    
    
    cl_uint ciDeviceCount;
    cl_device_id *devices;
    printf("OpenCL Device Info:\n");
    ciErrNum = clGetDeviceIDs (cpPlatform, CL_DEVICE_TYPE_ALL, 0, NULL, &ciDeviceCount);
    printf(" %u devices found supporting OpenCL:\n\n", ciDeviceCount);
    
    return 0;
}